package ips

import (
	"bufio"
	"math/rand/v2"
	"net"
	"os"
	"smart-flow/server/logger"
	"strings"
	"sync/atomic"

	"go.uber.org/zap"
)

var (
	cmIps   []string
	ipIndex atomic.Int64
)

func StartLoadCmIps(path string) {
	ipIndex.Store(rand.Int64N(int64(doLoadCmIps(path))))
}

func doLoadCmIps(path string) int {
	file, err := os.Open(path)
	if err != nil {
		logger.Logger.Error("加载移动ip地址池错误", zap.Error(err))
		return 0
	}
	defer func(file *os.File) {
		_ = file.Close()
	}(file)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		cidr := scanner.Text()
		if strings.TrimSpace(cidr) == "" {
			continue
		}
		if len(strings.Split(cidr, "/")) != 2 {
			continue
		}
		cmIps = append(cmIps, usableIps(cidr)...)
	}
	length := len(cmIps)
	logger.Logger.Info("加载移动ip地址池完成", zap.Int("count", length))
	return length
}

func usableIps(cidr string) []string {
	var ips []string
	_, ipNet, err := net.ParseCIDR(cidr) // 解析CIDR地址
	if err != nil {
		if ip := net.ParseIP(cidr); ip != nil { // 如果解析失败，尝试作为单独的IP地址处理（假设掩码为32）
			ips = append(ips, cidr)
		}
		return ips
	}
	ones, bits := ipNet.Mask.Size() // 从IPNet获取掩码大小
	if bits == 128 {                // IPv6地址处理
		ips = append(ips, ipNet.IP.String())
	}
	switch ones {
	case 32: // 如果是32位掩码，直接返回该IP
		ips = append(ips, ipNet.IP.String())
	case 31: // 如果是31位掩码，返回两个IP地址（特殊情况下没有网络地址和广播地址的区别）
		for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); incIp(ip) {
			ips = append(ips, ip.String())
		}
	default: // 对于其他掩码，需要排除网络地址和广播地址
		first := true
		for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); incIp(ip) {
			if first { // 跳过网络地址（第一个IP）
				first = false
				continue
			}
			nextIP := make(net.IP, len(ip))
			copy(nextIP, ip)
			incIp(nextIP)
			if !ipNet.Contains(nextIP) { // 检查是否为广播地址（最后一个IP），如果是则跳过
				break
			}
			ips = append(ips, ip.String())
		}
	}
	return ips
}

func incIp(ip net.IP) {
	for j := len(ip) - 1; j >= 0; j-- {
		ip[j]++
		if ip[j] > 0 {
			break
		}
	}
}

func GetCmIp() string {
	length := int64(len(cmIps))
	if length == 0 {
		return ""
	}
	index := ipIndex.Add(1) - 1
	return cmIps[index%length]
}
